// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package syscall

import (
	"internal/syscall/windows/sysdll"
	"sync"
	"sync/atomic"
	"unsafe"
)

// DLLError描述了DLL加载失败的原因。
type DLLError struct {
	Err     error
	ObjName string
	Msg     string
}

func (e *DLLError) Error() string { return e.Msg }

func (e *DLLError) Unwrap() error { return e.Err }

// 于..实施/运行时/系统调用\u windows。去

// 不推荐使用：改用SyscallN。
func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)

// 不推荐使用：改用SyscallN。
func Syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)

// 不推荐使用：改用SyscallN。
func Syscall9(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)

// 不推荐使用：改用SyscallN。
func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2 uintptr, err Errno)

// 不推荐使用：改用SyscallN。
func Syscall15(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2 uintptr, err Errno)

// 不推荐使用：改用SyscallN。
func Syscall18(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18 uintptr) (r1, r2 uintptr, err Errno)

func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)

// 一个DLL实现对单个DLL的访问。
type DLL struct {
	Name   string
	Handle Handle
}

// 我们使用它来计算系统上系统DLL的绝对路径
// 在搜索系统32不可用的地方。
var systemDirectoryPrefix string

func init() {
	n := uint32(MAX_PATH)
	for {
		b := make([]uint16, n)
		l, e := getSystemDirectory(&b[0], n)
		if e != nil {
			panic("Unable to determine system directory: " + e.Error())
		}
		if l <= n {
			systemDirectoryPrefix = UTF16ToString(b[:l]) + "\\"
			break
		}
		n = l
	}
}

// LoadDLL将命名的DLL文件加载到内存中。
// 
// 如果名称不是绝对路径，并且不是
// Go使用的已知系统DLL，Windows将在多个位置搜索命名的DLL，导致
// 潜在的DLL预加载攻击。
// 
// 在golang中使用LazyDLL。org/x/sys/windows以安全的方式加载系统DLL。
func LoadDLL(name string) (*DLL, error) {
	namep, err := UTF16PtrFromString(name)
	if err != nil {
		return nil, err
	}
	var h uintptr
	var e Errno
	if sysdll.IsSystemDLL[name] {
		absoluteFilepathp, err := UTF16PtrFromString(systemDirectoryPrefix + name)
		if err != nil {
			return nil, err
		}
		h, e = loadsystemlibrary(namep, absoluteFilepathp)
	} else {
		h, e = loadlibrary(namep)
	}
	if e != 0 {
		return nil, &DLLError{
			Err:     e,
			ObjName: name,
			Msg:     "Failed to load " + name + ": " + e.Error(),
		}
	}
	d := &DLL{
		Name:   name,
		Handle: Handle(h),
	}
	return d, nil
}

// MustLoadDLL类似于LoadDLL，但如果加载操作失败，它会惊慌失措。
func MustLoadDLL(name string) *DLL {
	d, e := LoadDLL(name)
	if e != nil {
		panic(e)
	}
	return d
}

// FindProc在DLL d中搜索名为name的过程，如果找到，则返回*Proc 
// 。如果搜索失败，它将返回一个错误。
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
	namep, err := BytePtrFromString(name)
	if err != nil {
		return nil, err
	}
	a, e := getprocaddress(uintptr(d.Handle), namep)
	if e != 0 {
		return nil, &DLLError{
			Err:     e,
			ObjName: name,
			Msg:     "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
		}
	}
	p := &Proc{
		Dll:  d,
		Name: name,
		addr: a,
	}
	return p, nil
}

// MustFindProc与FindProc类似，但如果搜索失败，它会惊慌失措。
func (d *DLL) MustFindProc(name string) *Proc {
	p, e := d.FindProc(name)
	if e != nil {
		panic(e)
	}
	return p
}

// Release从内存中卸载DLL d。
func (d *DLL) Release() (err error) {
	return FreeLibrary(d.Handle)
}

// Proc实现对DLL中的过程的访问。
type Proc struct {
	Dll  *DLL
	Name string
	addr uintptr
}

// Addr返回由p表示的过程的地址。
// 返回值可以传递给Syscall以运行该过程。
func (p *Proc) Addr() uintptr {
	return p.addr
}

// go:uintTrescapes 

// 调用使用参数a执行过程p。
// 
// 返回的错误始终为非零，由GetLastError的结果构造而成。
// 调用者必须先检查主返回值，以确定是否发生了错误
// （根据被调用的特定函数的语义），然后再咨询
// 错误。错误类型始终为syscall。呃，不。
// 
// 在amd64上，调用可以传递和返回浮点值。要传递带有C类型“float”的参数x，请使用
// uintptpr（math.float32位（x））。要传递C类型为
// /“double”的参数，请使用uintpttr（math.float64位（x））。浮点返回
// 在r2中返回值。C类型“float”的返回值是
// math。浮点32位（uint32（r2））。对于C类型的“double”，它是
// math。浮点64位（uint64（r2））。
func (p *Proc) Call(a ...uintptr) (uintptr, uintptr, error) {
	return SyscallN(p.Addr(), a...)
}

// LazyDLL实现对单个DLL的访问。
// 它将延迟DLL的加载，直到第一个
// 调用其Handle方法或其
// LazyProc的Addr方法之一。
// 
// LazyDLL受到与LoadDLL上记录的
// 相同的DLL预加载攻击。
// 
// 在golang中使用LazyDLL。org/x/sys/windows以安全的方式加载系统DLL。
type LazyDLL struct {
	mu   sync.Mutex
	dll  *DLL // 加载DLL后非零
	Name string
}

// 加载将DLL文件d.名称加载到内存中。如果失败，它将返回一个错误。
// 如果DLL已经加载到内存中，Load将不会尝试加载DLL。
func (d *LazyDLL) Load() error {
	// 非racy版本：
	// 如果d.dll==nil{
	if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) == nil {
		d.mu.Lock()
		defer d.mu.Unlock()
		if d.dll == nil {
			dll, e := LoadDLL(d.Name)
			if e != nil {
				return e
			}
			// 非racy版本：
			// d.dll=dll 
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
		}
	}
	return nil
}

// mustLoad与Load类似，但在搜索失败时会惊慌失措。
func (d *LazyDLL) mustLoad() {
	e := d.Load()
	if e != nil {
		panic(e)
	}
}

// Handle返回d的模块句柄。
func (d *LazyDLL) Handle() uintptr {
	d.mustLoad()
	return uintptr(d.dll.Handle)
}

// NewProc返回用于访问dll d中指定过程的LazyProc。
func (d *LazyDLL) NewProc(name string) *LazyProc {
	return &LazyProc{l: d, Name: name}
}

// NewLazyDLL创建与dll文件关联的新LazyDLL。
func NewLazyDLL(name string) *LazyDLL {
	return &LazyDLL{Name: name}
}

// 它延迟查找，直到调用Addr、Call或Find方法。
type LazyProc struct {
	mu   sync.Mutex
	Name string
	l    *LazyDLL
	proc *Proc
}

// Find在DLL中搜索名为p.Name的过程。如果搜索失败，则返回
// 错误。如果已经找到并加载到内存中，则Find将不会搜索过程，
// 。
func (p *LazyProc) Find() error {
	// 非racy版本：
	// 如果p.proc==nil{
	if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
		p.mu.Lock()
		defer p.mu.Unlock()
		if p.proc == nil {
			e := p.l.Load()
			if e != nil {
				return e
			}
			proc, e := p.l.dll.FindProc(p.Name)
			if e != nil {
				return e
			}
			// 非racy版本：
			// p.proc=proc 
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
		}
	}
	return nil
}

// mustFind与Find类似，但在搜索失败时会感到恐慌。
func (p *LazyProc) mustFind() {
	e := p.Find()
	if e != nil {
		panic(e)
	}
}

// Addr返回由p.
// 表示的过程的地址。返回值可以传递给Syscall以运行该过程。
func (p *LazyProc) Addr() uintptr {
	p.mustFind()
	return p.proc.Addr()
}

// go:uintprescapes 

// 调用使用参数a执行过程p。请参阅
// Proc的文档。打电话询问更多信息。
func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	p.mustFind()
	return p.proc.Call(a...)
}
